﻿namespace Mosquito.ViewModel.Collections {
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Mosquito.ViewModel.Collections.Tree;

    public class SortedObservableTree<TValue> : SortedObservableTreeBase<string, string, TValue, SortedObservableTree<TValue>> {
        public const string DefaultHierarchySeparator = ".";
        public static readonly Func<TValue, string> DefaultKeySelector = e => e.ToString();

        private readonly string hierarchySeparator;
        private readonly Func<TValue, string> keySelector;

        private bool isSelected;
        private SortedObservableTree<TValue> selectedTree;

        private bool isExpanded;

        public SortedObservableTree(Func<TValue, string> keySelector, string hierarchySeparator, IComparer<string> comparer)
            : base(comparer) {
            this.hierarchySeparator = hierarchySeparator ?? DefaultHierarchySeparator;
            this.keySelector = keySelector ?? DefaultKeySelector;
        }

        public SortedObservableTree(Func<TValue, string> keySelector, string hierarchySeparator)
            : this(keySelector, hierarchySeparator, Comparer<string>.Default) { }

        public SortedObservableTree(Func<TValue, string> keySelector)
            : this(keySelector, null) { }

        public SortedObservableTree()
            : this(null) { }

        public bool IsSelected {
            get {
                return this.isSelected;
            }

            set {
                if (!this.SetAndNotify(ref this.isSelected, value)) {
                    return;
                }

                if (value) {
                    this.SelectedTree = this;
                }
            }
        }

        public bool IsExpanded {
            get { return this.isExpanded; }
            set { this.SetAndNotify(ref this.isExpanded, value); }
        }

        public SortedObservableTree<TValue> SelectedTree {
            get {
                return this.selectedTree;
            }

            set {
                if (this.selectedTree != null && this.selectedTree != value) {
                    this.selectedTree.IsSelected = false;
                }

                if (!this.SetAndNotify(ref this.selectedTree, value)) {
                    return;
                }

                foreach (var child in this.ImmediateChildren) {
                    child.SelectedTree = value;
                }

                if (this.Parent != null) {
                    this.Parent.SelectedTree = value;
                }

                value.IsSelected = true;
            }
        }

        public void Add(TValue item) {
            var fullKey = this.keySelector(item);
            var relativeKey = this.GetRelativeKey(fullKey);

            this.Add(relativeKey, item);
        }

        public bool Remove(TValue item) {
            var fullKey = this.keySelector(item);
            var relativeKey = this.GetRelativeKey(fullKey);

            SortedObservableTree<TValue> node;

            if (!this.TryGetNode(relativeKey, out node)) {
                return false;
            }

            this.Remove(relativeKey, item);
            node.IsSelected = false;

            return true;
        }

        protected override SortedObservableTree<TValue> CreateNode() {
            return new SortedObservableTree<TValue>(this.keySelector, this.hierarchySeparator, this.comparer) {
                Parent = this
            };
        }

        protected override IEnumerable<string> SplitKey(string key) {
            return key.Split(new[] { this.hierarchySeparator }, StringSplitOptions.None);
        }

        protected override string JoinKey(IEnumerable<string> keyParts) {
            return string.Join(this.hierarchySeparator, keyParts);
        }

        private string GetRelativeKey(string fullKey) {
            var prefix = this.FullKey ?? string.Empty;

            if (!fullKey.StartsWith(prefix)) {
                throw new ArgumentException("The item's key does not contain it's parent's key as prefix.", "item");
            }

            if (this.FullKey == null) {
                return fullKey;
            }

            var prefixCount = this.SplitKey(prefix).Count();
            var parts = this.SplitKey(fullKey).Skip(prefixCount);
            fullKey = this.JoinKey(parts);

            return fullKey;
        }
    }
}